<template>
  <div
    class="v-select"
    :class="{
      'is-disabled': disabled,
    }"
    @click="toggleDropdown"
    @mouseenter="states.mouseHover = true"
    @mouseleave="states.mouseHover = false"
  >
    <Tooltip
      placement="bottom-start"
      manual
      :popper-options="popperOptions"
      ref="tooltipRef"
      @click-outside="controlDropdown(false)"
    >
      <Input
        v-model="states.inputValue"
        :disabled="disabled"
        :placeholder="filteredPlaceholder"
        ref="inputRef"
        :readonly="!filterable || !isDropdownShow"
        @input="debounceOnFilter"
        @keydown="handleKeydown"
      >
        <template #suffix>
          <Icon
            v-if="showClearIcon"
            icon="circle-xmark"
            class="v-input__inner"
            @click="handleClear"
            @mousedown.prevent="() => {}"
          />
          <Icon
            v-else
            icon="arrow-down"
            class="header-angle"
            :class="{ 'is-active': isDropdownShow }"
          />
        </template>
      </Input>
      <template #content>
        <div class="v-select__loading" v-if="states.loading">
          <Icon icon="spinner" />
        </div>
        <div
          class="v-select__nodata"
          v-else-if="filterable && !filteredOptions.length"
        >
          no matched data
        </div>
        <ul class="v-select__menu" v-else>
          <template v-for="(opt, index) in filteredOptions" :key="index">
            <li
              class="v-select__menu-item"
              :class="{
                'is-disabled': opt.disabled,
                'is-selected': opt.value === states.selectOption?.value,
                'is-highlighted': index === states.highlightIndex,
              }"
              :id="`select-item-${opt.value}`"
              @click.stop="optSelect(opt)"
            >
              <RenderVNode
                :VNode="renderLabel ? renderLabel(opt) : opt.label"
              />
              <!-- {{ opt.label }} -->
            </li>
          </template>
        </ul>
      </template>
    </Tooltip>
  </div>
</template>

<script lang="ts" setup>
import { reactive, ref, computed, watch } from 'vue'
import Tooltip from '../Tooltip/Tooltip.vue'
import Input from '../Input/Input.vue'
import Icon from '../Icon/Icon.vue'
import RenderVNode from '../Common/RenderVNode'

import type { Ref } from 'vue'
import type { TooltipInstance } from '../Tooltip/types'
import type { InputInstance } from '../Input/types'
import type {
  SelectProps,
  SelectEmits,
  SelectOption,
  SelectedStates,
} from './types'
import { debounce, isFunction } from 'lodash-es'

function findOption(value: string): SelectOption | null {
  const option = props.options.find((opt) => opt.value === value)

  return option ? option : null
}

const props = withDefaults(defineProps<SelectProps>(), { options: () => [] })
const emits = defineEmits<SelectEmits>()
const showClearIcon = computed(() => {
  // * hover 上去
  // * props.clearable 为 true
  // * 必须有过选项
  // * Input 值不能为空

  return (
    props.clearable &&
    states.mouseHover &&
    states.selectOption &&
    states.inputValue.trim() !== ''
  )
})
const filteredPlaceholder = computed(() => {
  return props.filterable && states.selectOption && isDropdownShow.value
    ? states.selectOption.label
    : props.placeholder
})
const timeout = computed(() => (props.remote ? 300 : 0))

const initialOption = findOption(props.modelValue)
// const innerValue = ref(initialOption ? initialOption.label : '')
const tooltipRef = ref() as Ref<TooltipInstance>
const isDropdownShow = ref(false)
const states = reactive<SelectedStates>({
  inputValue: initialOption ? initialOption.label : '',
  selectOption: initialOption,
  mouseHover: false,
  loading: false,
  highlightIndex: -1,
})

watch(
  () => props.options,
  (newOptions) => {
    filteredOptions.value = newOptions
  }
)

const popperOptions: any = {
  modifiers: [
    {
      name: 'offset',
      options: {
        offset: [0, 9],
      },
    },
    {
      name: 'sameWidth',
      enabled: true,
      fn({ state }: { state: any }) {
        state.style.popper.width = `${state.reacts.reference.width}px`
      },
      phase: 'beforeWrite',
      requires: ['computedStyles'],
    },
  ],
}
const filteredOptions = ref(props.options)
const inputRef = ref() as Ref<InputInstance>

const controlDropdown = (show: boolean) => {
  if (show) {
    // filter 模式
    // 选择过对应的值
    if (props.filterable && states.selectOption) {
      states.inputValue = ''
    }
    // 进行一次默认选项的生成
    if (props.filterable) {
      generateFilterOptions(states.inputValue)
    }
    tooltipRef.value?.show()
  } else {
    tooltipRef.value?.hide()
    // blur 的时候把之前选择的值回显到 input
    if (props.filterable) {
      states.inputValue = states.selectOption ? states.selectOption.label : ''
    }
    states.highlightIndex = -1
  }
  isDropdownShow.value = show
  emits('visible-change', show)
}
const toggleDropdown = () => {
  if (props.disabled) return
  if (isDropdownShow.value) {
    controlDropdown(false)
  } else {
    controlDropdown(true)
  }
}
const generateFilterOptions = async (searchValue: string) => {
  if (!props.filterable) return
  if (props.filterMethod && isFunction(props.filterMethod)) {
    filteredOptions.value = props.filterMethod(searchValue)
  } else if (
    props.remote &&
    props.remoteMethod &&
    isFunction(props.remoteMethod)
  ) {
    states.loading = true
    try {
      filteredOptions.value = await props.remoteMethod(searchValue)
    } catch (e) {
      console.log(e)
      filteredOptions.value = []
    } finally {
      states.loading = false
    }
  } else {
    filteredOptions.value = props.options.filter((opt) =>
      opt.label.includes(searchValue)
    )
  }
  states.highlightIndex = -1
}
const debounceOnFilter = debounce(() => {
  onFilter()
}, timeout.value)
const onFilter = () => {
  generateFilterOptions(states.inputValue)
}
const optSelect = (e: SelectOption) => {
  if (e.disabled) return
  // innerValue.value = e.label
  states.inputValue = e.label
  states.selectOption = e
  emits('update:modelValue', e.value)
  emits('change', e.value)
  controlDropdown(false)
  inputRef.value.ref.focus()
}
const handleClear = () => {
  states.selectOption = null
  states.inputValue = ''
  emits('update:modelValue', states.inputValue)
  emits('clear')
  emits('change', states.inputValue)
}
const handleKeydown = (e: KeyboardEvent) => {
  switch (e.key) {
    case 'Enter':
      if (!isDropdownShow.value) {
        controlDropdown(true)
      } else {
        if (
          states.highlightIndex > -1 &&
          filteredOptions.value[states.highlightIndex]
        ) {
          optSelect(filteredOptions.value[states.highlightIndex])
        } else {
          controlDropdown(false)
        }
      }
      break
    case 'Escape':
      if (isDropdownShow.value) {
        controlDropdown(false)
      }
      break
    case 'ArrowUp':
      // states.highlightIndex = -1
      if (filteredOptions.value.length) {
        if (states.highlightIndex === -1 || states.highlightIndex === 0) {
          states.highlightIndex = filteredOptions.value.length - 1
        } else {
          // move up
          states.highlightIndex--
        }
      }
      e.preventDefault()
      break
    case 'ArrowDown':
      // states.highlightIndex = -1
      if (filteredOptions.value.length) {
        if (
          states.highlightIndex === -1 ||
          states.highlightIndex === filteredOptions.value.length - 1
        ) {
          states.highlightIndex = 0
        } else {
          // move down
          states.highlightIndex++
        }
      }
      e.preventDefault()
      break
    default:
      break
  }
}

defineOptions({ name: 'VSelect' })
</script>
<style scoped></style>
